Skip to content

fix(type): restrict useWatch typed paths#799

Open
biubiukam wants to merge 2 commits into
react-component:masterfrom
biubiukam:fix/use-watch-types
Open

fix(type): restrict useWatch typed paths#799
biubiukam wants to merge 2 commits into
react-component:masterfrom
biubiukam:fix/use-watch-types

Conversation

@biubiukam

@biubiukam biubiukam commented Jun 17, 2026

Copy link
Copy Markdown

🤔 This is a ...

  • 🆕 New feature
  • 🐞 Bug fix
  • 📝 Site / documentation improvement
  • 📽️ Demo improvement
  • 💄 Component style improvement
  • 🤖 TypeScript definition improvement
  • 📦 Bundle size optimization
  • ⚡️ Performance optimization
  • ⭐️ Feature enhancement
  • 🌐 Internationalization
  • 🛠 Refactoring
  • 🎨 Code style optimization
  • ✅ Test Case
  • 🔀 Branch merge
  • ⏩ Workflow
  • ⌨️ Accessibility improvement
  • ❓ Other (about what?)

🔗 Related Issues

Related to ant-design/ant-design#58405.
close ant-design/ant-design#58405

💡 Background and Solution

Form.useWatch can infer watched value types from typed form instances, but the broad NamePath fallback overload also accepts unknown field paths when a typed form is provided. As a result, invalid field names can pass TypeScript checks.

This change keeps the runtime behavior unchanged and preserves the existing loose behavior for untyped forms. For typed form instances, the broad fallback overload is now limited to forms whose value type is any, so valid field paths continue to infer their value types while unknown top-level or nested paths are rejected by TypeScript.

The type coverage now verifies that:

  • a typed scalar field returns its declared value type;
  • a nested optional path preserves undefined in the return type;
  • unknown typed paths fail type checking.

📝 Change Log

Language Changelog
🇺🇸 English Improve Form.useWatch type checking for typed forms to reject unknown field paths.
🇨🇳 Chinese 改进 Form.useWatch 在类型化表单中的类型检查,未知字段路径将被拒绝。

✅ Test Plan

  • npm run lint:tsc
  • npm test -- tests/useWatch.test.tsx --runInBand
  • npm run lint
  • npm test -- --runInBand
  • npm run compile

Summary by CodeRabbit

发布说明

  • 功能改进

    • 优化 useWatch 的类型推导与依赖项签名支持,增强 TypeScript 校验的准确性与智能补全体验。
  • 测试

    • 扩展 useWatch 的编译期类型校验用例,新增字段/路径覆盖,并补充错误场景断言。
  • 文档

    • 更新示例中的 useWatch 订阅与输出内容,移除多余监听项并简化日志结果。

@vercel

vercel Bot commented Jun 17, 2026

Copy link
Copy Markdown

@biubiukam is attempting to deploy a commit to the React Component Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Jun 17, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d830c706-f76d-4c73-b483-0f380d2b09b3

📥 Commits

Reviewing files that changed from the base of the PR and between 38f2dca and fbfda21.

📒 Files selected for processing (2)
  • src/hooks/useWatch.ts
  • tests/useWatch.test.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/useWatch.test.tsx

Walkthrough

src/hooks/useWatch.ts 中新增 IsAnyAnyNamePath<TForm, ValueType> 条件类型,将 useWatchdependencies 参数从 NamePath 收敛为受 form 泛型约束的有效字段路径,非 any 泛型场景下对无效路径产生编译错误。补充 5 元组 dependencies 的重载支持。测试文件新增编译期断言工具类型及对应用例扩展测试字段覆盖,示例文件移除无关的 demo5/more 订阅。

Changes

useWatch 依赖项类型约束

Layer / File(s) Summary
条件类型工具与重载签名
src/hooks/useWatch.ts
新增 IsAny<T>IsEqual<X, Y>AnyNamePath<TForm, ValueType> 条件类型;新增 5 元组 dependencies 重载返回深层索引结果;两个 useWatch 重载的 dependencies 参数由 NamePath 改为 AnyNamePath<TForm>ValueType 重载为 TForm 新增默认类型参数 = FormInstance
类型断言工具与 typescript 测试用例
tests/useWatch.test.tsx
新增编译期断言工具类型 IsAny/Equal/ExpectFieldType 扩展 value3: string 与嵌套 path1?.path2?: number;新增对 value3['path1','path2']['deep','path1','path2','path3','path4']['dynamic','path']['demo']useWatch 类型推导断言;补充两处 @ts-expect-error 验证不存在字段 unknown 和非法嵌套路径 ['path1','unknown'] 触发编译错误;调整 JSON.stringify 输出字段。
示例文件清理
docs/examples/useWatch.tsx
移除对 demo5more['age', 'name', 'gender'])的 useWatch 订阅,并同步简化 console.log 输出内容,仅保留 hidden 相关订阅项。

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 分钟

Possibly related PRs

  • react-component/field-form#747: 同样修改 tests/useWatch.test.tsxuseWatch 相关功能,处理动态路径警告和 useWatch 类型推导逻辑。

Suggested reviewers

  • zombieJ

Poem

🐇 兔子跳过类型迷宫,
AnyNamePath 守护字段之门,
无效路径?编译器皱眉头!
@ts-expect-error 笑着点头,
类型安全,从此不再忧。
✨ 咚咚咚,字段推导稳如山!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR标题准确概括了主要变更:限制useWatch的类型化路径,与修复useWatch类型推导和字段验证的核心目标一致。
Linked Issues check ✅ Passed PR的代码变更完整解决了#58405中的两个问题:通过AnyNamePath类型工具实现字段类型推导,通过类型重载限制非类型化表单的字段路径验证。
Out of Scope Changes check ✅ Passed 所有变更均在PR目标范围内:useWatch.ts中的类型增强、useWatch.test.tsx中的类型测试补充,以及docs示例的同步更新,没有发现超出范围的变更。

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

src/hooks/useWatch.ts

ESLint skipped: missing config or dependency (missing-dependency). The ESLint configuration references a package that is not available in the sandbox.

tests/useWatch.test.tsx

ESLint skipped: the ESLint configuration for this file references a package that is not available in the sandbox.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces stricter type checking for the useWatch hook by defining IsAny and AnyNamePath helper types to validate dependencies against the form's generic type. It also updates the test suite and documentation to reflect these changes. The review feedback highlights a critical limitation: restricting the fallback overload to never prevents compilation for paths of length 5 or more, even when explicit type overrides are provided. To resolve this, the reviewer suggests refactoring AnyNamePath and the useWatch overloads to accept and respect an explicit ValueType parameter, allowing users to bypass the strictness for deep or dynamic paths.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread src/hooks/useWatch.ts Outdated
Comment on lines +16 to +18
type IsAny<T> = 0 extends 1 & T ? true : false;
type AnyNamePath<TForm extends FormInstance> =
IsAny<GetGeneric<TForm>> extends true ? NamePath : never;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

By restricting the fallback overload to never for all typed forms, any path of length 5 or more (which exceeds the explicit 4-level overloads) will fail to compile, even if the path is valid or if the user explicitly overrides the type parameter (e.g., useWatch<string>(['a', 'b', 'c', 'd', 'e'], form)).

We can make AnyNamePath more flexible by allowing it to accept NamePath if the user explicitly provides a custom ValueType (other than Store) or any. This preserves strict type checking for the default case while allowing explicit type overrides for deep or dynamic paths.

type IsAny<T> = 0 extends 1 & T ? true : false;
type AnyNamePath<TForm extends FormInstance, ValueType = Store> = 
  IsAny<GetGeneric<TForm>> extends true
    ? NamePath
    : IsAny<ValueType> extends true
      ? NamePath
      : Store extends ValueType
        ? never
        : NamePath;

Comment thread src/hooks/useWatch.ts
Comment on lines +85 to 88
function useWatch<ValueType = Store, TForm extends FormInstance = FormInstance>(
dependencies: AnyNamePath<TForm>,
form?: TForm | WatchOptions<TForm>,
): ValueType;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Pass the ValueType generic parameter to AnyNamePath so that explicit type overrides (e.g., useWatch<string>(...)) can bypass the never restriction for deep or dynamic paths.

Suggested change
function useWatch<ValueType = Store, TForm extends FormInstance = FormInstance>(
dependencies: AnyNamePath<TForm>,
form?: TForm | WatchOptions<TForm>,
): ValueType;
function useWatch<ValueType = Store, TForm extends FormInstance = FormInstance>(
dependencies: AnyNamePath<TForm, ValueType>,
form?: TForm | WatchOptions<TForm>,
): ValueType;

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in fbfda21.

The ValueType generic is now passed to AnyNamePath, so explicit return type overrides like useWatch(...) can support deep or dynamic paths while the default typed form path remains constrained.

@biubiukam

Copy link
Copy Markdown
Author

@afc163 这个 PR 是否需要调整呢

@codecov

codecov Bot commented Jun 20, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.54%. Comparing base (62af919) to head (38f2dca).

Additional details and impacted files
@@           Coverage Diff           @@
##           master     #799   +/-   ##
=======================================
  Coverage   99.54%   99.54%           
=======================================
  Files          20       20           
  Lines        1328     1328           
  Branches      329      325    -4     
=======================================
  Hits         1322     1322           
  Misses          6        6           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@afc163

afc163 commented Jun 20, 2026

Copy link
Copy Markdown
Member

看看 ai review 意见

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Form.useWatch未正常推导出类型且没有限制watch字段

2 participants